#####################################################
#  Python Watch and Warn Files Modification script  #
#####################################################
#		  Vallee Cedric			    #
#		    20/12/2011			    #
#	    Network and Systems Security 	    #
#####################################################

import re
import subprocess
import os
import hashlib
import smtplib
from email.MIMEText import MIMEText

#General analyser class : manage the mail sending 
class Analyser:
	#Constructor
	#mail : where the mail will be sent
	#dbpath : db location
	#fileList : List of files to analyse, ids.py and ids.cfg are automatically added
	def __init__(self, mail, dbpath,fileList):
		self.mail = mail
		self.dbpath = dbpath
		self.fileList = fileList
		self.fileList.append("ids.cfg")
		self.fileList.append("ids.py")
		#report: string which store the mail which will be sent
		self.report=""
		self.run()

	#send the report to the self.mail
	def sendMail(self):
		if self.report != "":
			#header			
			self.report="\tAutomatic report from ids.py script\n"+ self.report
			print self.report
			fromaddr = 'cvallee.insa@gmail.com'  
			toaddrs  = self.mail  
			msg = self.report 
			  
			# Credentials for connecting on the google spam account  
			username = 'nss.automatic.message@gmail.com'  
			password = 'ThisIsMyPassword'  
			  
			# The actual mail send
			server = smtplib.SMTP('smtp.gmail.com:587')  
			server.starttls()  
			server.login(username,password)  
			server.sendmail(fromaddr, toaddrs, msg)  
			server.quit()
			self.report=""
		else:
			print "No modification"
	#the main method	
	def Run(self):
		pass
	#database constructor
	def create(self):
		pass

#RpmAnalyser which is not yet implemented
class RpmAnalyser(Analyser):
	def Run(self):
		pass
	def Create(self):
		pass

#Analyser using the md5 hashcode for matching files
class MdAnalyser(Analyser):
	def create(self):
		#open the self.dbpath file in writing mode
		db= open(self.dbpath,'w')
		for i in self.fileList:
			try:
				#info=os.stat(i) brute information(same as ls)
				#if i is a directory then we compute the md5 of the ls -lia i string
				#if it's a file then we compute directly his md5
				if os.path.isdir(i):
					ls = subprocess.Popen(
					    ["ls", i],
					    stdout = subprocess.PIPE,
					    stderr = subprocess.PIPE
					)
					out , error = ls.communicate()
					m=hashlib.md5(out)
					hashTmp=m.hexdigest()+"  "+i+"\n"
				else:
					md5sum = subprocess.Popen(
					    ["md5sum", i],
					    stdout = subprocess.PIPE,
					    stderr = subprocess.PIPE
					)
					hashTmp, error = md5sum.communicate()
				#write it in the db file
				db.write(hashTmp)
			except:
				self.report+= "The %s file is missing\n"%i
		#Report the creation
		self.report+="The database has been created"
		db.close()

	def run(self):
		dbmodified=False
		if not os.path.exists(self.dbpath):
			#if the db file doesn't exist then we create it
			self.create()
		else:	
			#open, put the former md5 value in a list, and remove the '\n' char
			dbfile=open(self.dbpath,'r')
			reg=re.compile("(\w*)\s*([^\s]*)\n")
			db=dbfile.readlines()
			dbfile.close()
			db = map(lambda x: x.strip(),db)		
			#we look for each file of the config file
			for i in self.fileList:
				try:
					#info=os.stat(i) #brute information(same as ls)
					#if i is a directory then we compute the md5 of the ls -lia i string
					#if it's a file then we compute directly his md5
					if os.path.isdir(i):
						ls = subprocess.Popen(
						    ["ls", i],
						    stdout = subprocess.PIPE,
						    stderr = subprocess.PIPE
						)
						out , error = ls.communicate()
						m=hashlib.md5(out)
						hashTmp=m.hexdigest()+"  "+i
					else:
						md5sum = subprocess.Popen(
						    ["md5sum", i],
						    stdout = subprocess.PIPE,
						    stderr = subprocess.PIPE
						)
						hashTmp, error = md5sum.communicate()
						hashTmp=hashTmp[:-1]
					#if the current md5 value and the former one are different then the files have been modified 
					#so we put it in the report and update the md5 value to the new one
					if not hashTmp in db and not i == self.dbpath :
						#Update of the database
						for n in db:
							if i in n:
								db.remove(n)
								db.append(hashTmp)
								break
						dbmodified=True

						#Additionnal information for report
						#We join the ls -lia of the current file or directory that have been modified
						self.report+="File %s has been modificated\n"%i
						out, err = subprocess.Popen(
						    ["ls", "-lia", i],
						    stdout = subprocess.PIPE,
						    stderr = subprocess.PIPE
						).communicate()
						self.report+=out+"\n"
				except:
					self.report+= "The %s file is missing\n"%i
		if dbmodified:
			#if there was a modification of one of the files then we update the db file for the next call
			dbfile= open(self.dbpath,'w')
			dbfile.write("\n".join(db))
			self.report+="The database has been updated"
			dbfile.close()
		#we check if we need to send the report
		self.sendMail()

def main():
	try:
		cfgfile=open('ids.cfg','r')
	except :
		#raise Exception('config file not found\nIds abort\n')
		print '\tconfiguration file not found\n\tIds script aborted\n'
		return	
	mail=""
	rpm=False
	dbpath=""
	file=False
	fileList=[]
	#Processing the configuration file, and looking for the specials patterns
	for line in cfgfile:
		mailPat = re.compile("(email|e-mail)\s*:\s*([^\s]*)",re.IGNORECASE)
		pathPat = re.compile("(db|path)\s*:\s*([^\s]*)",re.IGNORECASE)
		lPat = re.compile("#.*list of files{0,1}.*#",re.IGNORECASE) 
		if file and line != '\n' and not line[0] == '#' :
			fileList.append(line[:-1])
		elif  mailPat.match(line):
			mail = mailPat.match(line).group(2)
		elif pathPat.match(line):
			dbpath=pathPat.match(line).group(2)
		elif re.match("option\s*:\s*rpm",line,re.IGNORECASE):
			rpm=True
		elif lPat.match(line):
			file=True		
		else:
			#Comment or unknown option
			pass
	if rpm:
		RpmAnalyser(mail,dbpath,fileList)
	else:
		MdAnalyser(mail,dbpath,fileList)
			
		
if __name__ == "__main__":
	main()
